并发提升 10 倍,运算延时降低 70%,领健从 ClickHouse 和 Kudu 到 Apache Doris 数仓升级实践
领健是健康科技行业 SaaS 软件的引领者,专注于消费医疗口腔和医美行业,为口腔诊所、医美机构、生美机构提供经营管理一体化系统,提供了覆盖单店管理、连锁管理、健康档案/电子病历、客户关系管理、智能营销、B2B交易平台、进销存、保险支付、影像集成、BI商业智能等覆盖机构业务全流程的一体化SaaS软件。同时通过开放平台连接产业上下游,与优质的第三方平台合作,为机构提供完整配套的一站式服务。截至当前,领健已经在全国设立了 20 余个分支机构,超过 30000 多家中高端以及连锁机构正在使用其服务。
Doris 在领健的演进历程
数据服务架构演进
项目需求
支持复杂查询:客户进行自助拖拽设计图表时,将生成一段复杂的 SQL 查询语句直查数据库,且语句复杂度未知,这将对数据库带来不小的压力,从而影响查询性能。
高并发低延时:至少可以支撑 100 个并发,并在 1 秒内得到查询结果;
数据实时同步:报表数据源自于 SaaS 系统,当客户对系统中的历史数据进行修改后,报表数据也要进行同步更改,保持一致,这就要求报表数据要与系统实现实时同步。
低成本易部署:SaaS 业务存在私有云客户,为降低私有化部署的人员及成本投入,这要求架构部署及运维要足够简单。
ClickHouse 遭遇并发宕机
最初项目选用 ClickHouse 来提供数据查询服务,但在运行过程中 ClickHouse 遭遇了严重的并发问题,即 10 个并发就会导致 ClickHouse 宕机,这使其无法正常为客户提供服务,这是迫使我们寻找可以替代 ClickHouse 产品的关键因素。
除此之外还有 2 个较为棘手的问题:
云上 ClickHouse 服务成本非常高,且 ClickHouse 组件依赖性较高,数据同步时 ClickHouse 和 Zookeeper 的频繁交互,会对稳定性产生较大的压力。
如何进行无缝迁移,不影响客户正常使用。
技术选型
针对存在的问题及需求,我们决定进行技术选型,分别对 Doris(0.14)、ClickHouse、Kudu 这 3 个产品展开的调研测试。
如上表所示,我们对这 3 个产品进行了横向比较,可以看出 Doris 在多方面表现优异:
高并发:Doris 并发性好,可支持上百甚至上千并发,轻松解决 10 并发导致 ClickHouse 宕机问题。
查询性能:Doris 可实现毫秒级查询响应,在单表查询中,虽 Doris 与 ClickHouse 查询性能基本持平,但在多表查询中,Doris 远胜于 ClickHouse ,Doris 可以实现在较高的并发下,QPS 不下降。
数据更新:Doris 的数据模型可以满足我们对数据更新的需求,以保障系统数据和业务数据的一致性,下文将详细介绍。
使用成本:Doris 架构简单,整体部署简单快速,具有完备的导入功能,很好的弹性伸缩能力;同时, Doris 内部可以自动做副本平衡,运维成本极低。而 Clickouse 及 Kudu 对组件依赖较高,在使用上需要做许多准备工作,这就要求具备一支专业的运维支持团队来处理大量的日常运维工作。
标准 SQL:Doris 兼容 MySQL 协议,使用标准 SQL,开发人员上手简单,不需要付出额外的学习成本。
分布式 Join :Doris 支持分布式 Join,而 ClickHouse 由于 Join 查询限制、函数局限性、以及可维护性较差等原因,不满足我们当前的业务需求。
社区活跃:Apache Doris 是国内自研数据库,开源社区相当活跃,同时 SelectDB 为 Doris 社区提供了专业且全职团队做技术支持,遇到问题可以直接与社区联系沟通,并能得到快速解决,这对于国外的项目,很大地降低与社区沟通的语言与时间成本。
从以上调研中可以发现,Doris 各方面能力优秀,十分符合我们对选型产品的需求,因此我们使用 Doris 替代了 ClickHouse ,解决了ClickHouse 并发性能差、宕机等问题,很好的支撑了数据报表查询服务。
数仓架构演进
早期数仓架构
从上图可知,数据通过 Kafka Consumer 进入 ODS 层;通过 Kudu 层满足数据更新需要;运用 Impala 来执行数据运算和查询;通过自研平台 DMEP 进行任务调度。在 ETL 代码中会使用大量的 Upsert 对数据进行 Merge 操作,那么引入 Doris 的首要问题就是要如何实现 Merge 操作,支持业务数据更新,下文中将进行介绍。
新数仓架构
如上图所示,在新架构设计中使用 Apache Doris 负责数仓存储及数据运算;实时数据、 ODS数据的同步从 Kafka Consumter 改为 Flink ;流计算平台使用团队自研的 Duckula;任务调度则引入最新 的 DolphinSchedular,Dolphin schedule 几乎涵盖了自研 DMEP 的大部分功能,同时可以很方便拓展 ETL 的方式,可调度很多不同的任务。
优化实践
数据模型选择
Replace_if_not_null
方式进行数据更新时,可以实现单独更新一列,代码如下:drop table test.expamle_tbl2
CREATE TABLE IF NOT EXISTS test.expamle_tbl2
(
`user_id` LARGEINT NOT NULL COMMENT "用户id",
`date` DATE NOT NULL COMMENT "数据灌入日期时间",
`city` VARCHAR(20) COMMENT "用户所在城市",
`age` SMALLINT COMMENT "用户年龄",
`sex` TINYINT COMMENT "用户性别",
`last_visit_date` DATETIME REPLACE_IF_NOT_NULL COMMENT "用户最后一次访问时间",
`cost` BIGINT REPLACE_IF_NOT_NULL COMMENT "用户总消费",
`max_dwell_time` INT REPLACE_IF_NOT_NULL COMMENT "用户最大停留时间",
`min_dwell_time` INT REPLACE_IF_NOT_NULL COMMENT "用户最小停留时间"
)
AGGREGATE KEY(`user_id`, `date`, `city`, `age`, `sex`)
DISTRIBUTED BY HASH(`user_id`) BUCKETS 1
PROPERTIES (
"replication_allocation" = "tag.location.default: 1"
);
insert into test.expamle_tbl2
values(10000,'2017-10-01','北京',20,0,'017-10-01 06:00:00',20,10,10);
select * from test.expamle_tbl ;
insert into test.expamle_tbl2 (user_id,date,city,age,sex,cost)
values(10000,'2017-10-01','北京',20,0,50);
select * from test.expamle_tbl ;
如下图所示,当写 50 进去,可以实现只覆盖Cost
列,其他列保持不变。
Doris Compaction 优化
当 Flink 抽取业务库全量数据、持续不断高频写入 Doris 时,将产生了大量数据版本,Doris 的 Compaction 合并版本速度跟不上新版本生成速度,从而造成数据版本堆积。从下图可看出,BE Compaction Score 分数很高,最高可以达到 400,而健康状态分数应在 100 以下。
全量数据不使用实时写入的方式,先导出到 CSV,再通过 Stream Load 写入 Doris;
降低 Flink 写入频率,增大 Flink 单一批次数据量;该调整会降低数据的实时性,需与业务侧进行沟通,根据业务方对实时性的要求调整相关数值,最大程度的降低写入压力。
调节 Doris BE 参数,使更多 CPU 资源参与到 Compaction 操作中;
compaction_task_num_per_disk
单磁盘 Compaction 任务线程数默认值 2,提升后会大量占用CPU资源,阿里云 16 核,提升 1 个线程多占用 6% 左右 CPU。max_compaction_threads compaction
线程总数默认为10。max_cumulative_compaction_num_singleton_deltas 参数控制一个 CC 任务最多合并 1000 个数据版本,适当改小后单个 Compaction 任务的执行时间变短,执行频率变高,集群整体版本数会更加稳定。
通过调整集群, Compaction Score 稳定在了 50-100,有效解决了版本堆积问题。
负载隔离
最初我们只有 1 个 Doris 集群,Doris 集群要同时支持高频实时写、 高并发查询、ETL 处理以及Adhoc 查询等功能。其中高频实时写对 CPU 的占用很高,而 CPU 的上限决定高并发查询的能力,另外 Adhoc 查询无法预知 SQL 的复杂度,当复杂度过高时也会占用较高的内存资源,这就导致了资源竞争,业务之间互相影响的问题。为解决这些问题,我们进行了以下探索优化。
1. Doris 集群拆分
最初我们尝试对 Doris 集群进行拆分,我们把 1 个集群拆分为 3 个集群,分别为 ODS 集群、DW 集群、ADS 集群。我们将 CPU 负载最高的 ODS 层分离出去, ETL 时,通过 Doris 外表连接另一个 Doris 集群抽取数据;同时也将 BI 应用访问的集群分离出去,独立为业务提供数据查询。如下所示为各集群负责的任务:
ODS 集群:数仓 ODS 层,Flink 写数据集中在此层进行。
DW 集群:数仓 DW 层,DIM 层,主要负责 ETL 处理,Adhoc 查询任务。
ADS 集群:数仓 ADS 层,主要支持 Web 应用的数据查询
2. 资源隔离优化集群资源
升级到 Doris 0.15 后,我们将 ODS 表的副本修改为group_ods
3 份,default
3 份。Flink 写入时只写group_ods
资源组的节点,数据写入后,得益于 Doris 内部的副本同步机制,数据会自动实时同步到default
资源组。ETL 则可以使用default
资源组的节点资源取用 ODS 数据,进行查询和数据处理。同理 ADS 也做了相同处理,原先需要通过外表进行数据抽取同步的表,均被做成了副本跨资源组的形式。此方式有效缩短了跨集群数据同步的 ETL 时长 。
离线 ETL 内存高
Host is down
或者Fail to initialize storage reader
。在1.0 及更高版本中, Doris 由于优化了内存跟踪,则容易见到以下报错:Memory exceed limit. Used: XXXX ,Limit XXXX.1. 优化调整 Join 的方式:
从上图可知,Join 类型优先级从左往右依次变低,Shuffle 的优先级最低,排在 Broadcast 之后。值得注意的是, Broadcast 内存开销非常大,它将右表广播到所有 BE 节点,这相当于每个 BE 节点会消耗一个右表的内存,这将造成很大的内存开销。针对 Broadcast 比较大的内存开销,我们通过 Hint 条件强制 Join 类型的方式,使 Join 语句跳过 Broadcast 到 Shuffle Join ,从而降低内存消耗。
select * from a join [shuffle] b on a.k1 = b.k1;
分批须知:需要将分批的标记列放在主键中,最大程度提升搜索数据的效率;注意分桶和分区的设置方式,保证每个分区的数据量都比较均衡,避免个别分区内存占用较高的问题。
总结
新架构收益
基于 Doris 的新数仓架构不再依赖 Hadoop 生态组件,运维简单,维护成本低。
具有更高性能,使用更少的服务器资源,提供更强的数据处理能力。
支持高并发,能直接支持 WebApp 的查询服务。
支持外表,可以很方便的进行数据发布,将数据推送其他数据库中。
支持动态扩容,数据自动平衡。
支持多种联邦查询方式,支持 Hive、ES、MySQL 等
社区寄语
其次,Doris 是一款国人自研的的 MPP 架构分析型数据库,这令我感到很自豪,同时其社区十分活跃、便于沟通,Doris 背后的商业化公司 SelectDB 为社区组建了一支专职技术团队,任何问题都能在 1 小时内得到响应,近 1 年社区更是在 SelectDB 的持续推动下,推出了一系列十分抗打的新特性。另外社区在版本迭代时会认真考虑中国人的使用习惯,这些会为我们的使用带来很多便利。
最后,感谢 Doris 社区和 SelectDB 团队的全力支持,也欢迎开发者以及各企业多多了解 Doris、使用 Doris,支持国产数据!
Doris 1.2.0 传送门
下载安装:(复制到浏览器打开)
GitHub下载:
https://github.com/apache/doris/releases
官网下载页:
https://doris.apache.org/download
https://github.com/apache/doris/releases/tag/1.2.0-rc04
最后,欢迎更多的开源技术爱好者加入 Apache Doris 社区,携手成长,共建社区生态。Apache Doris 社区当前已容纳了上万名开发者和使用者,承载了 30+ 交流社群,如果你也是 Apache Doris 的爱好者,扫码加入 Apache Doris 社区用户交流群,在这里你可以获得:
专业全职团队技术支持 直接和社区专家交流,获取免费且专业回复 认识不同行业的开发者,收获知识以及合作机会 Apache Doris 最新版本优先体验权 获取一手干货和资讯以及活动优先参与权
▶ 全面进化!Apache Doris 1.2.0 Release 版本正式发布
▶ DDL 毫秒级同步,Light Schema Change 的设计与实现